from typing import Any, Dict, Literal, NoReturn

__all__ = ['undefined', 'JavascriptObject']


class _undefined:
    """Mimics the `undefined` object in JavaScript.
    """

    def __bool__(self) -> Literal[False]:
        return False

    def __int__(self) -> Literal[0]:
        return 0

    def __repr__(self) -> Literal['undefined']:
        return 'undefined'

    def __str__(self) -> Literal['undefined']:
        return 'undefined'


undefined = _undefined()


class JavascriptObject:
    """Mimics an Object in JavaScript.

    Args:
        - `d` (Dict[str, Any], optional): [description]. Defaults to None.
    """

    def __init__(self, d: Dict[str, Any] = None):
        self._dict: Dict[str, Any] = d or {}

    def __setattr__(self, name: str, value: Any) -> NoReturn:
        if name == '_dict':
            super().__setattr__('_dict', value)
            return
        self._dict[name] = value

    def __getattribute__(self, name: str) -> Any:
        if name == '_dict':
            return super().__getattribute__('_dict')
        return self._dict.get(name, undefined)

    def __setitem__(self, name: str, value: Any) -> NoReturn:
        self._dict[name] = value

    def __getitem__(self, name: str) -> Any:
        return self._dict.get(name, undefined)

    def __dict__(self):
        return self._dict

    def __str__(self) -> str:
        return str(self._dict)

    def __repr__(self) -> str:
        return 'JavascriptObject(%s)' % str(self._dict)

    def __eq__(self, o: object) -> bool:
        if not isinstance(o, self.__class__):
            return False
        return self._dict == o._dict

    def __ne__(self, o: object) -> bool:
        return not self.__eq__(o)

    def __dir__(self) -> list:
        return list(self._dict)
